﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using Extractor.Manager.Entities;
using Infrastructure;
using Infrastructure.Listener;
using Infrastructure.Logging;
using SceneUnpacker.Schematrix;
using SceneUnpacker.se.gno;
using SceneUnpacker.Schematrix;

namespace Extractor.Manager
{
    public class ExtractorManager
    {
        /// <summary>
        /// logger
        /// </summary>
        private static readonly ILog log = LogManager.GetLogger( MethodBase.GetCurrentMethod().DeclaringType );

        #region Private Methods

        private bool _keepAlive;
        private readonly List<FolderListener> _folderListeners;
        private readonly ThreadsManager<ArchiveFile> _rarFileWorker;
        private readonly HashSet<string> _foundedfiles;

        private const string RarFileType = "^.*\\.rar$|^*.001$";

        #region SingleTone

        private static readonly object SynceObject = new object();
        private static ExtractorManager _instance;
        #endregion

        #endregion

        public static ExtractorManager Intance
        {
            get
            {
                lock (SynceObject)
                {
                    if (_instance == null)
                    {
                        log.Info( "Create instance of Watcher" );
                        _instance = new ExtractorManager();
                    }
                }
                return _instance;
            }
        }

        public bool Started
        {
            get { return _keepAlive; }
        }

        public event EventHandler<FoundedFileEventArgs> RarFileFouned;

        public event EventHandler<DoneExtractingEventArgs> DoneExtracting;

        public event EventHandler<ExtractionProgressEventArgs> ProgressChanged;

        private ExtractorManager ()
        {
            _keepAlive = false;
            _foundedfiles = new HashSet<string>();

            _folderListeners = new List<FolderListener>();
            _rarFileWorker = new ThreadsManager<ArchiveFile>( 100, 1 );
            _rarFileWorker.HandleWorkEvent += RarFileWorkerHandleWorkEvent;
        }

        public void Add ( string path )
        {
            var folderListener = new FolderListener( path, "*.*", 5, 180000 );
            folderListener.IgnoreReportedFiles = true;
            folderListener.FileReceived += OnFolderListenerFileReceived;
            _folderListeners.Add( folderListener );

            if (Started)
            {
                folderListener.StartAsync();
            }
        }

        public void Start ()
        {
            log.Info( "starting archive watcher" );

            _keepAlive = true;
            foreach (var folderListener in _folderListeners)
            {
                folderListener.StartAsync();
            }
            _rarFileWorker.StartAsync( true );
        }

        public void Stop ()
        {
            _keepAlive = false;
            foreach (var folderListener in _folderListeners)
            {
                folderListener.Stop();
            }
            _rarFileWorker.Stop();
        }

        #region Private Methods

        private void OnFolderListenerFileReceived ( FolderListener folderListener, ReceivedFileInfo receivedFileInfo )
        {
            receivedFileInfo.DeleteOnComplete = false;

            if (AlreadyExtract( receivedFileInfo.FullName ))
            {                
                return;
            }

            var isRarFile = Regex.Match( receivedFileInfo.FullName, RarFileType, RegexOptions.IgnoreCase ).Success;
            if (isRarFile)
            {
                var archiveCollection = new ArchiveCollection();
                archiveCollection.ArchiveFound += ArchiveCollectionArchiveFound;
                archiveCollection.ContentChanged += archiveCollection_ContentChanged;
                archiveCollection.Destination = new DirectoryInfo( Path.GetDirectoryName( receivedFileInfo.FullName ) );
                archiveCollection.Source = new DirectoryInfo( Path.GetDirectoryName( receivedFileInfo.FullName ) );                
            }
        }

        void archiveCollection_ContentChanged ( ArchiveCollection sender )
        {

        }

        private void ArchiveCollectionArchiveFound ( object sender, Archive a )
        {
            if (!_foundedfiles.Contains( a.File.FullName ))
            {
                _foundedfiles.Add( a.File.FullName );
            }

            log.InfoFormat( "Rar file recived {0}", a.File.FullName );
            var archiveFile = new ArchiveFile( a );
            var fileEventArgs = new FoundedFileEventArgs( archiveFile );
            InvokeFileFouned( fileEventArgs );

            log.InfoFormat( "Insert file [{0}] to extraction queue", a.File.FullName );
            _rarFileWorker.InsertWorkItem( archiveFile );
            _foundedfiles.Add( archiveFile.Archive.File.FullName );
            
        }

        private void RarFileWorkerHandleWorkEvent ( ThreadsManager<ArchiveFile>.Handle handle )
        {
            if (!Started)
                return;

            try
            {
                if (handle.Item.Status != Status.Ignored)
                {
                    log.InfoFormat( "preparing for extracting file [{0}]", handle.Item.FullFileName );

                    handle.Item.MarkedAsExtracting();
                    string directory = Path.GetDirectoryName( handle.Item.FullFileName );
                    string destination = Path.Combine( directory, "Extracted" );
                    handle.Item.Archive.Unrar.ExtractionProgress += Unrar_ExtractionProgress;
                    handle.Item.Archive.unpack( new DirectoryInfo( destination ) );

                    MarkFileAsDone( handle.Item.FullFileName );
                    handle.Item.MarkedAsDone( destination );

                    InvokeDoneExtracting( new DoneExtractingEventArgs( handle.Item ) );
                    log.InfoFormat( string.Format( "End extracting fllow for file [{0}]", handle.Item.FullFileName ) );
                }
                else
                {
                    log.InfoFormat( "Ignore file [{0}]", handle.Item.FullFileName );
                }
            }
            catch (Exception ex)
            {
                log.Error( string.Format( "Failed extracting file {0}", handle.Item.FullFileName ), ex );
            }
        }

        private void Unrar_ExtractionProgress ( object sender, ExtractionProgressEventArgs e )
        {
            ProgressChanged( this, e );
        }

        private void MarkFileAsDone ( string filename )
        {
            try
            {
                log.DebugFormat( "Marking file [{0}] as Done", filename );

                File.Create( GetDoneFileName( filename ) );
            }
            catch (Exception ex)
            {
                log.Error( "Failed create file [{0}] Done", ex );
            }
        }

        private bool AlreadyExtract ( string filename )
        {
            string fileNameDone = GetDoneFileName( filename );
            if (File.Exists( fileNameDone ))
            {
                log.DebugFormat( "file [{0}] alredy extracted", filename );
                return true;
            }
            return false;
        }

        private string GetDoneFileName ( string filename )
        {
            string foundedFileDir = Path.GetDirectoryName( filename );
            string foundedFileName = Path.GetFileNameWithoutExtension( filename );
            string fileNameDone = string.Format( "{0}\\{1}.done", foundedFileDir, foundedFileName );
            return fileNameDone;
        }

        private void InvokeFileFouned ( FoundedFileEventArgs fileName )
        {
            EventHandler<FoundedFileEventArgs> handler = RarFileFouned;
            if (handler != null) handler( this, fileName );
        }

        private void InvokeDoneExtracting ( DoneExtractingEventArgs e )
        {
            EventHandler<DoneExtractingEventArgs> handler = DoneExtracting;
            if (handler != null) handler( this, e );
        }

        #endregion
    }
}
